﻿#include "businessfile.h"

#include <nlohmann/json.hpp>
#include <SyncManager/syncmanager.h>
#include "businessnetwork.h"
#include <UserManager/usermanager.h>
#include <AppConfig/appconfig.h>
#include <FileManager/filemanager.h>
#include <CipherUtils/cipherutils.h>
#include <iostream>
#include <chrono>
#include "businessviewmodel.h"
#include <Debug/DEBUG.hpp>
#include "businessmsghandler.h"
#include <protocol/protocolConverter.h>
#include <sstream>
#include "businessinit.h"

BusinessFile::BusinessFile() {}

void BusinessFile::uploadFile(QJsonObject &in, QJsonObject &out)
{
    BusinessNetWork &net = BusinessNetWork::getInstance();
    AppConfig &config = AppConfig::getInstance();
    UserManager &userManager = UserManager::getInstance();

    nlohmann::json messageJson;
    messageJson["FileSize"] = in["FileSize"].toInteger();
    messageJson["FileName"] = in["FileName"].toString().toStdString();
    messageJson["FileType"] = in["FileType"].toInteger();
    DEBUG_INFO((std::string) "send file info: " + (std::string) "FileName: " + (std::string)messageJson["FileName"] + " FileSize: " + std::to_string((uint64_t)messageJson["FileSize"]));
    SyncManager &syncManager = SyncManager::getInstance();

    User &self = userManager.getTargetUser(config.runtimeValue("Account"));
    std::string messageStr = messageJson.dump();

    // apply for a unique id
    uint64_t id = net.requestUniqueID();
    DEBUG_INFO("file id: " + std::to_string(id));
    // send to server
    SyncPoint point = syncManager.generateUniqueSyncPoint();
    net.sendMessageToServer(3, 1, self["UserID"], id, point,
                            (unsigned char *)messageStr.data(), messageStr.size());

    bool res = syncManager.waitForSyncPoint(point);
    out["result"] = false;
    if (!res)
    {
        std::string message = "transfer rejected!!!";
        BusinessViewModel::getInstance().updateFileTransferInfo(message);
        // transmission rejected
        return;
    }

    FileManager &fileManager = FileManager::getInstance();
    CipherUtils &utils = CipherUtils::getInstance();

    std::string filePath = in["FilePath"].toString().toStdString();

    uint64_t fileSize = in["FileSize"].toInteger();
    uint64_t fileBlocks = fileSize / fileManager.FILE_BLOCK_SIZE;
    // send every block of file

    auto startTime = std::chrono::high_resolution_clock::now();
    std::chrono::high_resolution_clock::time_point lastUpdateTime = std::chrono::high_resolution_clock::now();
    BusinessInit& ini = BusinessInit::getInstance();
    for (uint64_t i = 0; i <= fileBlocks; i++)
    {
        if (ini.isStop())
            return;
        nlohmann::json messageToSend;
        int blockOffset = i;
        int offset = blockOffset * fileManager.FILE_BLOCK_SIZE;
        auto files = fileManager.readFromDisk(filePath, blockOffset, fileManager.FILE_BLOCK_SIZE, false);

        std::string fileBase64 = utils.Base64_Encode(files.first.get(), files.second);

        messageToSend["UserID"] = self["UserID"];
        messageToSend["TransmissionID"] = id;
        messageToSend["BlockOffset"] = i;
        messageToSend["FileBase64"] = fileBase64;
        messageToSend["BlockSize"] = files.second;

        std::string messageStr = messageToSend.dump();
        SyncPoint filePoint = syncManager.generateUniqueSyncPoint();

        net.sendMessageToServer(3, 3, id, 0, filePoint,
                                (unsigned char *)messageStr.data(), messageStr.size());

        // update info
        auto nowTime = std::chrono::high_resolution_clock::now();
        auto timeSinceLastUpdate = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - lastUpdateTime).count();

        // 如果两次更新的时间小于200ms，则跳过此次更新
        if (timeSinceLastUpdate < 200) {

        } else {
            lastUpdateTime = nowTime;
            // update info
            std::stringstream ss;
            auto duration_us = std::chrono::duration_cast<std::chrono::microseconds>(nowTime - startTime);
            double seconds = (double)duration_us.count() / 1e6;
            double megaBytes = (offset + files.second) / 1024.0 / 1024.0;
            double speed = seconds > 0 ? megaBytes / seconds : 0.0; // Avoid division by zero by checking if seconds > 0

            uint64_t timeLeft = 0;
            if (speed > 1e-6)
            {
                timeLeft = (uint64_t)(((double)messageJson["FileSize"] - (offset + files.second)) / (speed * 1024 * 1024));
            }

            ss << "uploading file\n"
               << "File name: " << messageJson["FileName"] << "\n"
               << "progress: " << (double)(offset + files.second) / (double)messageJson["FileSize"] * 100 << "%\n"
               << "speed: " << speed << " (MB/s)\n"
               << "remaining time: " << timeLeft << " s";

            BusinessViewModel::getInstance().updateFileTransferInfo(ss.str());
            // wait for handl success
        }
        syncManager.waitForSyncPoint(filePoint);
    }

    net.sendMessageToServer(3, 4, self["UserID"], 0, id,
                            nullptr, 0);
    std::string message = "transfer success!!!";
    out["result"] = true;
    BusinessViewModel::getInstance().updateFileTransferInfo(message);
}

void BusinessFile::downloadFile(QJsonObject &in, QJsonObject &out)
{
    int messageType = in["FileType"].toInt();

    QString fileName = in["FileName"].toString();
    uint64_t fileID;
    if (messageType == 1) {
        // 如果是普通的文件，则直接查询该文件的id
        fileID = FileName2ID[fileName.toStdString()];
    } else {
        // 如果是聊天的图片，则名字就是编号
        fileID = fileName.toULongLong();
    }

    BusinessNetWork &net = BusinessNetWork::getInstance();
    SyncManager &syncManager = SyncManager::getInstance();
    SyncPoint point = syncManager.generateUniqueSyncPoint();

    net.sendMessageToServer(3, 2, fileID, messageType, point,
                            nullptr, 0);

    if (!syncManager.waitForSyncPoint(point))
    {
        // rejected
        return;
    }

    if (messageType == 2) {
        // 对于聊天的图片，需要同步等待图片传输完成
        uint64_t syncPoint = syncManager.generateUniqueSyncPoint();
        out["SyncPoint"] = std::to_string(syncPoint).c_str();
        registerPictureTrans(fileID, syncPoint);
    }
}

void BusinessFile::addFileNameAndID(const std::string &name, uint64_t id)
{
    FileName2ID[name] = id;
}

void BusinessFile::recvUploadFileRequest(std::shared_ptr<Protocol> protocol)
{
    nlohmann::json fileInfo = BusinessMsgHandler::getJsonOfMessage(protocol);
    ProtocolConverter converter;
    converter.setProtocolOption(BusinessNetWork::getInstance().getProtocolOption());
    converter.parseData((unsigned char *)protocol->data());

    uint64_t id = std::get<int64_t>(converter.getHeaderField(3));
    int type = fileInfo["FileType"];
    std::string path;
    if (type == 1) {
        path = "./files/" + (std::string)fileInfo["FileName"];
    } else {
        path = "./picture/" + (std::string)fileInfo["FileName"];
    }

    SyncPoint point = std::get<int64_t>(converter.getHeaderField(4));
    DEBUG_INFO("new file: " + path + " size: " + std::to_string((uint64_t)fileInfo["FileSize"]));

    // accept
    FileManager::getInstance().registerID(id, path, fileInfo["FileSize"]);
    BusinessNetWork::getInstance().sendMessageToServer(4, 3, 0, 0, point,
                                                       nullptr, 0);
    ID2Time[id].first = std::chrono::high_resolution_clock::now(); // start time
    ID2Time[id].second = std::chrono::high_resolution_clock::now();// last updateTime

    FileInfo[id] = fileInfo;
}

void BusinessFile::recvFileBlock(std::shared_ptr<Protocol> protocol)
{
    FileManager &fileManager = FileManager::getInstance();
    nlohmann::json dataJson = BusinessMsgHandler::getJsonOfMessage(protocol);

    fileManager.commitToQueue(dataJson);

    ProtocolConverter converter;
    converter.setProtocolOption(BusinessNetWork::getInstance().getProtocolOption());
    converter.parseData((unsigned char *)protocol->data());

    SyncPoint point = std::get<int64_t>(converter.getHeaderField(4));

    BusinessNetWork::getInstance().sendMessageToServer(5, 2, 0, 0, point,nullptr, 0);
    uint64_t id = std::get<int64_t>(converter.getHeaderField(2));

    std::chrono::high_resolution_clock::time_point& lastUpdateTime = ID2Time[id].second;

    auto startTime = ID2Time[id].first;

    int blockOffset = dataJson["BlockOffset"];
    // update info
    uint64_t offset = blockOffset * fileManager.FILE_BLOCK_SIZE;
    std::stringstream ss;
    std::chrono::high_resolution_clock::time_point nowTime = std::chrono::high_resolution_clock::now();
    auto timeSinceLastUpdate = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - lastUpdateTime).count();

    // 如果两次更新的时间小于100ms，则跳过此次更新
    if (timeSinceLastUpdate < 200) {

    } else {
        lastUpdateTime = nowTime;

        auto duration_us = std::chrono::duration_cast<std::chrono::microseconds>(nowTime - startTime);
        double seconds = (double)duration_us.count() / 1e6;
        double megaBytes = (offset) / 1024.0 / 1024.0;
        double speed = seconds > 0 ? megaBytes / seconds : 0.0; // Avoid division by zero by checking if seconds > 0

        uint64_t timeLeft = 0;
        if (speed > 1e-6)
        {
            timeLeft = (uint64_t)(((double)FileInfo[id]["FileSize"] - (offset)) / (speed * 1024 * 1024));
        }

        ss << "downloading file\n"
           << "File name: " << FileInfo[id]["FileName"] << "\n"
           << "progress: " << (double)(offset / (double)FileInfo[id]["FileSize"]) * 100 << "%\n"
           << "speed: " << speed << " (MB/s)\n"
           << "remaining time: " << timeLeft << " s";

        BusinessViewModel::getInstance().updateFileTransferInfo(ss.str());
    }
}

void BusinessFile::recvEndFileTransmission(std::shared_ptr<Protocol> protocol)
{
    // 1.upload file info to database
    // 2.erase fileManager
    ProtocolConverter converter;
    converter.setProtocolOption(BusinessNetWork::getInstance().getProtocolOption());
    converter.parseData((unsigned char *)protocol->data());
    // get file id
    uint64_t transmissionID = std::get<int64_t>(converter.getHeaderField(4));
    // get file name with filePath
    FileManager &fileManager = FileManager::getInstance();
    std::string filePath = fileManager.queryFilePath(transmissionID);
    // upload filename
    // erase fileManager
    fileManager.finishID(transmissionID);
    ID2Time.erase(transmissionID);
    FileInfo.erase(transmissionID);
    std::string str = "transfer success!!!";
    BusinessViewModel::getInstance().updateFileTransferInfo(str);

    // 如果是图片，则删除图片
    int k = filePath.find_last_of('/');
    std::string fileName = filePath.substr(k + 1);
    uint64_t picName = str2ULL(fileName);
    tryReleasePictureTrans(picName);
}


uint64_t BusinessFile::str2ULL(const std::string& str) {
    uint64_t res = 0;
    for (auto i : str) {
        if (i >= '0' && i <= '9') {
            res = res * 10 + i - '0';
        } else {
            return 0;
        }
    }
    return res;
}


void BusinessFile::refreshFileList(QJsonObject &in, QJsonObject &out)
{
    BusinessNetWork &net = BusinessNetWork::getInstance();

    net.sendMessageToServer(3, 7, 0, 0, 0, nullptr, 0);
}

void BusinessFile::registerPictureTrans(uint64_t transmissionID, uint64_t syncPoint)
{
    picSyncStore[transmissionID] = syncPoint;
}

void BusinessFile::tryReleasePictureTrans(uint64_t transmissionID)
{
    if (picSyncStore.count(transmissionID)) {
        SyncManager::getInstance().releaseSyncPoint(picSyncStore[transmissionID], true);
        picSyncStore.erase(transmissionID);
    }
}
